package rua.jose.macamo;

import java.util.Random;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.Vibrator;
import android.view.Display;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;

public class Puzzle extends Activity {
   PuzzleView game;

   @Override
   public void onCreate(final Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); //Portrait orientation
      requestWindowFeature(Window.FEATURE_NO_TITLE); //No title-bar
      //Enable full screen:
      getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
      final Display d = getWindowManager().getDefaultDisplay();
      final Bundle b = getIntent().getExtras();
      final String photo = b.getString("photo"); //Get the filename for the photo
      game = new PuzzleView(this, photo, d.getWidth(), d.getHeight());
      setContentView(game);
      takeKeyEvents(true);
   }
   @Override
   public boolean onKeyDown(final int keyCode, final KeyEvent msg) {
      if (keyCode == KeyEvent.KEYCODE_BACK) //If back was pressed
         finish(); //Kill the activity
      return true; //Tell the system that the event was handled
   }
   @Override
   public boolean onKeyUp(final int keyCode, final KeyEvent msg) {
      return true; //Tell the system that the event was handled
   }

   @Override
   protected Dialog onCreateDialog(final int id) {
      AlertDialog.Builder dlgAlert;
      dlgAlert = new AlertDialog.Builder(this);
      if (id == 1) {
         dlgAlert.setTitle("Congratulations");
         dlgAlert.setMessage("You win!");
         dlgAlert.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
            public void onClick(final DialogInterface dialog, final int which) {
               Toast.makeText(getApplicationContext(), "Touch the screen to return", Toast.LENGTH_LONG).show();
               game.finished = true;
            }
         });
         dlgAlert.setCancelable(false);
      }
      return dlgAlert.create();
   }
}

class PuzzleView extends View {
   Random rami = new Random();
   Context mcontext;
   //Graphics-related:
   final short pTop, pBottom; //Top & Bottom of the playfield
   Paint myPaint = new Paint();
   Piece[][] pieces;
   Piece drag;
   byte level = 3; //How many pieces there will be per side
   float pwidth; //Width of a piece
   float pheight; //Height of a piece
   float px, py; //x & y of the floating piece
   int pi, pj; //Original location of the floating piece
   Vibrator v;
   boolean finished = false; //"The picture is finished" -flag

   /**
    * Initialize the PuzzleView.
    * @param context Reference to Context.
    * @param photo The name of the would-be puzzle image.
    * @param width Width of the screen in pixels.
    * @param height Height of the screen in pixels.
    */
   public PuzzleView(final Context context, final String photo, final int width, final int height) {
      super(context);
      this.mcontext = context;
      setFocusable(true);
      myPaint.setStrokeWidth(0); //Line width is always 1px (yes, 1px), no matter what density
      myPaint.setColor(Color.BLACK);
      myPaint.setStyle(Paint.Style.STROKE);
      v = (Vibrator)mcontext.getSystemService(Context.VIBRATOR_SERVICE); //Get a reference to the vibrator
      final short picHeight = (short)(width/3d*4d); //Height of the playfield (with the 3:4 aspect ratio)
      final short offset = (short)(height - picHeight); //The possible extra space outside the playfield
      //Calculate the playfield boundaries:
      if (offset > 0) { //If there is extra space:
         pTop = (short)(offset/2d); //Half of the offset goes at the top
         pBottom = (short)(pTop + picHeight); //Half of the offset goes at the bottom
      }
      else { //If there's no offset, or we're dealing with some strange aspect ratio:
         pTop = 0; //Take the whole screen as playfield
         pBottom = (short) height;
      }
      //Process the image:
      final int photoid = getResources().getIdentifier("drawable/"+photo, null, mcontext.getPackageName());
      Bitmap orig = BitmapFactory.decodeResource(getResources(), photoid); //Load the picture
      final float scale = ((float)width) / orig.getWidth();
      final Matrix m = new Matrix();
      m.setScale(scale, scale);
      orig = Bitmap.createBitmap(orig, 0, 0, orig.getWidth(), orig.getHeight(), m, true);
      cutPic(orig); //Slice the picture to pieces
      orig.recycle(); //Unload the full picture
      orig = null;
      randomizePic(); //Randomise the pieces
   }

   /**
    * Cuts the given bitmap to pieces, stored in pieces[][].
    * @param org Original image.
    */
   public void cutPic(final Bitmap org) {
      pieces = new Piece[level][level]; //Discard the (possible) previous level
      pheight = org.getHeight()/level; //Calculate the piece dimensions
      pwidth = org.getWidth()/level;
      int temp = 1;
      //From top to bottom, left to right
      for (int j = 0; j < level; j++) {
         for (int i = 0; i < level; i++) {
            pieces[i][j] = new Piece(Bitmap.createBitmap(org, (int)pwidth*i, (int)pheight*j, (int)pwidth, (int)pheight), temp);
            temp++;
         }
      }
   }

   /**
    * Scramble the pieces.
    */
   public void randomizePic() {
      Piece temp;
      boolean stillComplete = true;
      while (stillComplete) { //Randomize until it's not fully assembled:
         for (int i = 0; i < level; i++) {
            for (int j = 0; j < level; j++) {
               final int m = rami.nextInt(level);
               final int n = rami.nextInt(level);
               temp = pieces[i][j];
               pieces[i][j] = pieces[m][n];
               pieces[m][n] = temp;
            }
         }
         stillComplete = checkWin();
      }
   }

   /**
    * Check if the picture is complete.
    * @return true, if the picture is complete, false if not.
    */
   public boolean checkWin() {
      int temp = 1;
      for (int j = 0; j < level; j++) {
         for (int i = 0; i < level; i++) {
            if (pieces[i][j].number != temp)
               return false;
            temp++;
         }
      }
      return true;
   }

   /**
    * Check if the piece above the given coordinate is in right place related to this one.
    * @param x Horizontal coordinate
    * @param y Vertical coordinate
    * @return True, if the piece is in its right place.
    */
   public boolean checkUp(final int x, final int y) {
      if (y > 0 && pieces[x][y].number == pieces[x][y-1].number+level)
         return true;
      return false;
   }
   public boolean checkDown(final int x, final int y) {
      if (y < level-1 && pieces[x][y].number == pieces[x][y+1].number-level)
         return true;
      return false;
   }
   public boolean checkLeft(final int x, final int y) {
      if (x > 0 && pieces[x][y].number == pieces[x-1][y].number+1)
         return true;
      return false;
   }
   public boolean checkRight(final int x, final int y) {
      if (x < level-1 && pieces[x][y].number == pieces[x+1][y].number-1)
         return true;
      return false;
   }

   @Override
   public boolean onTouchEvent(final MotionEvent e) {
      if (finished) { //If the picture is finished, quit the game:
         ((Activity)mcontext).runOnUiThread(new Runnable() {
            @Override
            public void run() {
               ((Activity)mcontext).finish();
            }
         });
         return true;
      }

      switch (e.getAction()) {
         case MotionEvent.ACTION_DOWN: //Touchscreen was pressed:
            if (e.getY() > pTop && e.getY() < pBottom) { //If on the playfield:
               v.vibrate(25);
               pi = (int)(e.getX()/pwidth);
               pj = (int)((e.getY()-pTop)/pheight);
               drag = pieces[pi][pj]; //Create a floating piece
               pieces[pi][pj].lifted = true;
               px = e.getX();
               py = (e.getY()-pTop);
            }
            break;
         case MotionEvent.ACTION_MOVE: //Finger was moved:
            if (drag != null) { //If a floating piece exists, move it:
               px = e.getX();
               py = (e.getY()-pTop);
            }
            break;
         case MotionEvent.ACTION_UP: //Finger was lifted:
            if (e.getY() > pTop && e.getY() < pBottom && drag != null) { //If on the playfield:
               v.vibrate(30);
               pieces[pi][pj] = pieces[(int)(e.getX()/pwidth)][(int)((e.getY()-pTop)/pheight)];
               pieces[(int)(e.getX()/pwidth)][(int)((e.getY()-pTop)/pheight)] = drag;
               pieces[pi][pj].lifted = false;
               drag.lifted = false;
               if (checkWin())
                  ((Activity)mcontext).showDialog(1);
            }
            else if (drag != null) //If outside the playfield:
               drag.lifted = false;
            drag = null;
            break;
      } //switch
      invalidate(); //Repaint the screen
      return true; //Tell the system that the event was handled
   }
   @Override
   public boolean onKeyDown(final int keyCode, final KeyEvent msg) {
      return true; //Tell the system that the event was handled
   }
   @Override
   public boolean onKeyUp(final int keyCode, final KeyEvent msg) {
      return true; //Tell the system that the event was handled
   }
   @Override
   public boolean onTrackballEvent(final MotionEvent e) {
      return true; //Tell the system that the event was handled
   }

   @Override
   public void onDraw(final Canvas c) {
      c.drawColor(Color.BLACK); //Erase the previous frame with black
      for (int i = 0; i < level; i++) { //Draw the pieces:
         for (int j = 0; j < level; j++) {
            if (!pieces[i][j].lifted) {
               c.drawBitmap(pieces[i][j].pic, pwidth*i, pTop+pheight*j, myPaint);
               //Check if there's a right piece next to this one, and draw a line separator if not:
               if (!checkUp(i, j))
                  c.drawLine(pwidth*i, pTop+pheight*j, pwidth*(i+1), pTop+pheight*j, myPaint);
               if (!checkDown(i, j))
                  c.drawLine(pwidth*i, pTop+pheight*(j+1), pwidth*(i+1), pTop+pheight*(j+1), myPaint);
               if (!checkLeft(i, j))
                  c.drawLine(pwidth*i, pTop+pheight*j, pwidth*i, pTop+pheight*(j+1), myPaint);
               if (!checkRight(i, j))
                  c.drawLine(pwidth*(i+1), pTop+pheight*j, pwidth*(i+1), pTop+pheight*(j+1), myPaint);
            }
         }
      }

      if (drag != null) { //Draw the floating piece, if exists:
         c.drawBitmap(drag.pic, px-pwidth/2, pTop+py-pheight/2, myPaint);
         c.drawRect(px-pwidth/2, pTop+py-pheight/2, px+pwidth/2, pTop+py+pheight/2, myPaint);
      }
   }
}

class Piece {
   protected Bitmap pic;
   protected boolean lifted = false; //Is the piece floating
   protected final int number; //ID-number

   /**
    * 
    * @param picture Holds the piece image.
    * @param idNumber ID number of the piece.
    */
   public Piece(final Bitmap picture, final int idNumber) {
      this.pic = picture;
      this.number = idNumber;
   }
}